/*
 * Copyright (C) 2010 TopCoder Inc., All Rights Reserved.
 */
package com.hercules.xre.protocol.testing.netty.tcp;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;

import com.hercules.xre.protocol.testing.Helper;
import com.topcoder.util.log.Log;
import com.topcoder.util.log.LogManager;

/**
 * <p>
 * Class responsible for gathering entire XRE message before passing it to the next handler.
 * This handler reads first 4 bytes of received data, these bytes contain length of
 * serialized events workload. If there is not enough data, handler stops processing of an
 * event and waits for more data to come.
 * </p>
 * <p>
 * <strong>Thread-safety:</strong> This class is thread safe as it's immutable and it's parent class is thread safe.
 * </p>
 * @author TCSDESIGNER, TCSDEVELOPER
 * @version 1.0
*/
public class TCPFrameDecoder extends FrameDecoder {
    /**
     * <p>
     * Logger that is used to log activities and errors.
     * It is initialized to LogManager.getLog(TCPFrameDecoder.class.getName()).
     * Can not be null, never changes after initialization.
     * </p>
    */
    private static final Log LOGGER = LogManager.getLog(TCPFrameDecoder.class.getName());

    /**
     * Represents the length of the header.
     */
    private static final int HEADER_LENGTH = 4;

    /**
     * Default constructor, does nothing.
    */
    public TCPFrameDecoder() {
    }

    /**
     * <p>
     * Method responsible for checking if received data represents entire message
     * or maybe only a portion of it (and thus application should wait for more data to come).
     * </p>
     * @param buffer buffer containing received data, can't be null
     * @param ctx channel handler context for handled message, can't be null
     * @param channel channel to which message has been sent, can't be null
     * @return If received data represents entire message a buffer containing message is
     *         returned (without 4 byte header denoting message length), if there is not
     *         enough data a null is received to signals that application should wait for
     *         more data.
     * @throws IllegalArgumentException if any argument is invalid
    */
    protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
        final String signature = "TCPFrameDecoder#decode";
        Helper.logEnter(signature, new String[] {"ChannelHandlerContext", "channel", "buffer"},
            new Object[] {ctx, channel, buffer}, LOGGER);
        Helper.checkNull(ctx, "ctx", signature, LOGGER);
        Helper.checkNull(channel, "channel", signature, LOGGER);
        Helper.checkNull(buffer, "buffer", signature, LOGGER);
        // Make sure if the length field was received.
        if (buffer.readableBytes() < HEADER_LENGTH) {
            // The length field was not received yet - return null.
            // This method will be invoked again when more packets are received and appended to the buffer.
            return Helper.logExit(signature, null, LOGGER);
        }

        // The length field is in the buffer.

        // Mark the current buffer position before reading the length field
        // because the whole frame might not be in the buffer yet.
        // We will reset the buffer position to the marked position if there's not enough bytes in the buffer.
        buffer.markReaderIndex();

        // Read the length field.
        int length = buffer.readInt();

        // Make sure if there's enough bytes in the buffer.
        if (buffer.readableBytes() < length) {
            // The whole bytes were not received yet - return null.
            // This method will be invoked again when more packets are received and appended to the buffer.
            // Reset to the marked position to read the length field again next time.
            buffer.resetReaderIndex();

            return Helper.logExit(signature, null, LOGGER);
        }

        // There's enough bytes in the buffer. Read it.
        ChannelBuffer frame = buffer.readBytes(length);
        // Successfully decoded a frame. Return the decoded frame.
        return Helper.logExit(signature, frame, LOGGER);
    }
}

